/**
 * Licensed under the MIT License
 *
 * Copyright (c) 2010 Alexandre Croiseaux
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package marcel.display.gui.components
{
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import marcel.display.View;
	import marcel.utils.MathUtils;
	import swc.form.*;
	
	/**
	* Dispatched when the value has changed
	* @eventType flash.events.Event.CHANGE
	*/
	[Event(name = "change", type = "flash.events.Event")]

	/**
	 * A Scroller component class to use in lists and comboboxes.
	 * The graphics are editable inside the form.fla file
	 * @author Alexandre Croiseaux
	 * @author Benoit Saint-Maxent
	 */
	public class Scroller extends View
	{
		//--------------------------------------------------------------------------
		//
		//  Public vars
		//
		//--------------------------------------------------------------------------
		/**
		 * indicates the speed of scroll when the up and down buttons are pressed, or when the mouse wheel is used
		 */
		public var speed:Number = 5;
		
		//--------------------------------------------------------------------------
		//
		//  Private vars
		//
		//--------------------------------------------------------------------------
		/**
		 * @private
		 */
		protected var _bg:MovieClip;
		/**
		 * @private
		 */
		protected var _upBtn:MovieClip;
		/**
		 * @private
		 */
		protected var _downBtn:MovieClip;
		/**
		 * @private
		 */
		protected var _tracker:MovieClip;
		/**
		 * @private
		 */
		protected var _min:Number;
		/**
		 * @private
		 */
		protected var _max:Number;
		/**
		 * @private
		 */
		protected var _scrollView:DisplayObject;
		/**
		 * @private
		 */
		protected var _mouseWheel:Boolean;
		/**
		 * @private
		 */
		protected var _dir:int;
		
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		/**
		 * Constructs a new Scroller instance
		 * @param	height	The height of the scroller, in pixels
		 * @param	trackOnly	Indicates if up and down arrow buttons must be created
		 */
		public function Scroller(height:uint, trackOnly:Boolean = false)
		{
			createAssets();
			buildUI(height, trackOnly);
			addEventListener(Event.ADDED_TO_STAGE, onAdded);
			addEventListener(Event.REMOVED_FROM_STAGE, onRemoved);
		}
		
		//--------------------------------------------------------------------------
		//
		//  Public methods
		//
		//--------------------------------------------------------------------------
		/**
		 * Returns the value of the current scroller
		 * @return	A number between 0(top) and 1(bottom)
		 */
		public function getValue():Number
		{
			return MathUtils.rescale(_tracker.y, [_bg.y, _bg.y + _bg.height - _tracker.height], [0, 1], true);
		}
		
		/**
		 * Sets the value of the current scroller
		 * @param	pos	A number between 0(top) and 1(bottom)
		 */
		public function setValue(pos:Number):void
		{
			var y:Number = _bg.y + (_bg.height - _tracker.height) * pos;
			move(0, y);
		}
		
		/**
		 * Indicates a displayobject instance that must be scrolled when the scroller value changes
		 * @param	view	A DisplayObject instance to move
		 * @param	minY	the minimum y position of the display object (when the scroller is on top)
		 * @param	maxY	the minimum y position of the display object (when the scroller is at bottom)
		 */
		public function scrollView(view:DisplayObject, minY:Number, maxY:Number):void
		{
			_min = minY;
			_max = maxY;
			_scrollView = view;
			onMoveTracker(null);
		}
		
		/**
		 * Indicates the number of steps needed by the scroller to go from top to bottom.
		 */
		public function set steps(steps:uint):void
		{
			speed = _bg.height / (steps + 1);
		}
		
		/**
		 * Activates or deactivates the mouse wheel for the current scroller
		 * @param	bEnabled	true to enable the mouse wheel, false otherwise
		 */
		public function enableMouseWheel(bEnabled:Boolean):void
		{
			_mouseWheel = bEnabled;
		}
		
		/**
		 * Updates the height of the current scroller
		 * @param	h	the height value in pixels
		 */
		public function setHeight(h:uint):void
		{
			_bg.height = contains(_upBtn) ? h - _upBtn.height - _downBtn.height : h;
			_bg.y = contains(_upBtn) ? _upBtn.height : 0;
			_downBtn.y = h;
			_tracker.height = _bg.height / 3;
			_tracker.y = _bg.y;
		}
		
		/**
		 * Releases the current scroller by removing all the listeners
		 */
		public function release():void
		{
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMoveTracker);
			stage.removeEventListener(MouseEvent.MOUSE_UP, onReleaseTracker);
			stage.removeEventListener(MouseEvent.MOUSE_WHEEL, onWheel);
			_upBtn.removeEventListener(Event.ENTER_FRAME, onFrame);
			_downBtn.removeEventListener(Event.ENTER_FRAME, onFrame);
		}
		
		/**
		 * Resizes the tracker to the specified percent of the scroller height
		 * @param	prc	A percent value in the range [0-1]
		 */
		public function setTrackerPercentHeight(prc:Number):void
		{
			var h:uint = _bg.height * prc;
			h = MathUtils.clamp(h, 15, _bg.height);
			_tracker.height = h;
			if (_tracker.y < _bg.y) _tracker.y = int(_bg.y);
			else if (_tracker.y > _bg.y + _bg.height - _tracker.height) _tracker.y = int(_bg.y + _bg.height - _tracker.height);
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Private methods
		//
		//--------------------------------------------------------------------------
		/**
		 * @private
		 */
		protected function createAssets():void
		{
			_upBtn = new ScrollBtnTopAsset();
			_downBtn = new ScrollBtnTopAsset();
			_bg = new ScrollBgAsset();
			_tracker = new ScrollTrackAsset();
		}
		
		private function buildUI(height:uint, trackOnly:Boolean):void
		{
			_bg.addEventListener(MouseEvent.CLICK, onClickBg);
			addChild(_bg);
			
			_upBtn.buttonMode = true;
			_upBtn.addEventListener(MouseEvent.MOUSE_DOWN, onPressUp);
			if (!trackOnly) addChild(_upBtn);
			
			_downBtn.buttonMode = true;
			_downBtn.scaleY = -1;
			_downBtn.addEventListener(MouseEvent.MOUSE_DOWN, onPressDown);
			if (!trackOnly) addChild(_downBtn);
			
			_tracker.addEventListener(MouseEvent.MOUSE_DOWN, onPressTracker);
			_tracker.addEventListener(MouseEvent.MOUSE_UP, onReleaseTracker);
			addChild(_tracker);
			
			setHeight(height);
		}
		
		private function onWheel(e:MouseEvent):void
		{
			if (_mouseWheel) move(e.delta < 0 ? 1 : -1);
		}
		
		private function move(direction:int, pos:int = -1):void
		{
			_tracker.y += direction * speed;
			if (pos != -1) _tracker.y = int(pos);
			if (_tracker.y < _bg.y) _tracker.y = int(_bg.y);
			else if (_tracker.y > _bg.y + _bg.height - _tracker.height) _tracker.y = int(_bg.y + _bg.height - _tracker.height);
			onMoveTracker(null);
		}
		
		//--------------------------------------------------------------------------
		//
		//  Events
		//
		//--------------------------------------------------------------------------
		private function onPressTracker(e:MouseEvent):void
		{
			_tracker.startDrag(false, new Rectangle(0, _bg.y, 0, _bg.height - _tracker.height));
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onMoveTracker);
			stage.addEventListener(MouseEvent.MOUSE_UP, onReleaseTracker);
		}
		
		private function onMoveTracker(e:MouseEvent):void
		{
			if (_scrollView) _scrollView.y = int(_min + ((_max - _min) * getValue()));
			dispatchEvent(new Event(Event.CHANGE));
		}
		
		private function onReleaseTracker(e:MouseEvent):void
		{
			_tracker.stopDrag();
			stage.removeEventListener(Event.ENTER_FRAME, onFrame);
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMoveTracker);
			stage.removeEventListener(MouseEvent.MOUSE_UP, onReleaseTracker);
		}
		
		private function onPressUp(e:MouseEvent):void
		{
			_dir = -1;
			stage.addEventListener(Event.ENTER_FRAME, onFrame);
			stage.addEventListener(MouseEvent.MOUSE_UP, onReleaseTracker);
		}
		
		private function onPressDown(e:MouseEvent):void
		{
			_dir = 1;
			stage.addEventListener(Event.ENTER_FRAME, onFrame);
			stage.addEventListener(MouseEvent.MOUSE_UP, onReleaseTracker);
		}
		
		private function onFrame(e:Event):void
		{
			move(_dir);
		}
		
		private function onClickBg(e:MouseEvent):void
		{
			move(0, _bg.mouseY);
		}
		
		private function onRemoved(e:Event):void
		{
			release();
		}
		
		private function onAdded(e:Event):void
		{
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, onWheel);
		}
	}

}